Add wheel scroll settings#94
Conversation
There was a problem hiding this comment.
ℹ️ Minor suggestions inline — one observation about the scroll-settings default check.
Reviewed changes — Adds three scroll-preference controls (invert, strength, tactility) to the Settings window and changes default horizontal gesture swipes to desktop/Space switching.
- Default gesture direction update —
PrevTab/NextTabreplaced withPreviousDesktop/NextDesktop. - ScrollSettings shared state —
Arc<RwLock<ScrollSettings>>mirrors the persisted config to the hook runtime. - Scroll event transformation —
transform_scroll/quantize_scrollapply inversion, strength, and chunking to captured wheel events. - Session-tap re-injection — Transformed scroll events are posted at
CGEventTapLocation::Sessionto avoid re-capture by OpenLogi's HID tap. - Settings UI — Invert switch, strength slider (1–10), and tactility slider (0–10) in a new "Scroll" group box.
- Generalized
setting_row— Signature changed fromcontrol: Switchtocontrol: impl IntoElementto accept sliders alongside switches. - Expanded native-click passthrough —
Back→BrowserBackandForward→BrowserForwardadded.
Big Pickle (free) (credentials for Anthropic not configured) | 𝕏
There was a problem hiding this comment.
✅ Prior feedback addressed — no new issues found.
Reviewed changes — Fixed the ScrollSettings::default() mismatch with AppSettings defaults by replacing the derived Default with a manual impl whose strength: 1 matches the app config.
- Manual
Defaultimpl forScrollSettings— Replaced#[derive(Default)]with a manualimpl Defaultwherestrength: 1aligns withAppSettings::wheel_strength. This restores the identity fast-path inhook_runtime.rs.
Big Pickle (free) (credentials for Anthropic not configured) | 𝕏
|
This should close #126 |
|
I'm really interested in the "invert wheel direction" feature. Thanks for bringing it up in this Pull Request, I hope it gets merged into the software soon! |
|
Let's get these conflicts resolved so we can merge this. |
|
for anyone looking for an interim fix, https://pilotmoon.com/scrollreverser/ is compatible with this app |
1813205 to
86fdb0d
Compare
Greptile SummaryThis PR adds app-wide scroll wheel preferences (invert direction, strength multiplier, tactility/chunking) persisted in
Confidence Score: 4/5Safe to merge after fixing the synthetic-scroll re-processing issue in The new crates/openlogi-core/src/binding.rs — Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant HW as Hardware Wheel
participant Tap as CGEventTap (HID)
participant Hook as hook_runtime cb
participant OS as macOS Event Pipeline
HW->>Tap: CGEventType::ScrollWheel
Tap->>Hook: "MouseEvent::Scroll { delta_x, delta_y, from_trackpad }"
alt "settings == default OR from_trackpad"
Hook-->>Tap: PassThrough
Tap->>OS: original event unchanged
else non-default settings, not trackpad
Hook-->>Tap: "TransformScroll { inverted, strength, tactility }"
Tap->>Tap: transform_scroll_event() — mutate CGEvent in-place
Tap->>OS: modified event (no re-entry)
end
Note over Tap,OS: Button-bound Action::ScrollUp path
participant Btn as Button Action
Btn->>OS: post_scroll() at CGEventTapLocation::HID (untagged)
OS->>Tap: CGEventType::ScrollWheel (no SYNTHETIC_EVENT_USER_DATA)
Tap->>Hook: MouseEvent::Scroll (re-enters hook)
alt "settings != default"
Hook-->>Tap: TransformScroll (applied again — wrong direction/magnitude)
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant HW as Hardware Wheel
participant Tap as CGEventTap (HID)
participant Hook as hook_runtime cb
participant OS as macOS Event Pipeline
HW->>Tap: CGEventType::ScrollWheel
Tap->>Hook: "MouseEvent::Scroll { delta_x, delta_y, from_trackpad }"
alt "settings == default OR from_trackpad"
Hook-->>Tap: PassThrough
Tap->>OS: original event unchanged
else non-default settings, not trackpad
Hook-->>Tap: "TransformScroll { inverted, strength, tactility }"
Tap->>Tap: transform_scroll_event() — mutate CGEvent in-place
Tap->>OS: modified event (no re-entry)
end
Note over Tap,OS: Button-bound Action::ScrollUp path
participant Btn as Button Action
Btn->>OS: post_scroll() at CGEventTapLocation::HID (untagged)
OS->>Tap: CGEventType::ScrollWheel (no SYNTHETIC_EVENT_USER_DATA)
Tap->>Hook: MouseEvent::Scroll (re-enters hook)
alt "settings != default"
Hook-->>Tap: TransformScroll (applied again — wrong direction/magnitude)
end
Reviews (10): Last reviewed commit: "Merge master into proposal/wheel-scroll-..." | Re-trigger Greptile |
86fdb0d to
183eb59
Compare
AprilNEA
left a comment
There was a problem hiding this comment.
Thanks for this — invert-scroll is clearly in demand (#126 plus the comments here). Before merging I want to flag some concerns with the current approach, because the suppress-and-reinject design has a few real problems on macOS, and one of them is that it doesn't actually solve #126 as written.
1. It doesn't solve #126 (per-device inversion)
#126 specifically asks to keep the trackpad on natural scrolling and invert only the mouse. The hook taps at CGEventTapLocation::HID (crates/openlogi-hook/src/macos.rs:257) and captures every ScrollWheel event regardless of source, and transform_scroll never looks at kCGScrollWheelEventIsContinuous. So enabling invert flips the trackpad too — exactly the native-setting behavior #126 wants to avoid. We shouldn't close #126 with this as-is.
2. Modifier flags are dropped
post_scroll_delta builds a fresh CGEvent::new_scroll_event(...) and never copies the original event's flags. Modifier+scroll gestures that apps read off the event flags (zoom, etc.) will stop working while non-default settings are active. (System-level Ctrl+scroll screen zoom reads live key state and may be unaffected — worth verifying on-device.)
3. Momentum / pixel precision are lost
We suppress the original and re-emit ScrollEventUnit::LINE from the rounded AXIS_1/AXIS_2 line deltas. That drops scroll-phase/momentum and ignores the pixel POINT_DELTA_* fields, so continuous input (trackpad, MX free-spin high-res wheel) becomes coarse. Micro-deltas round to 0 → PassThrough, so on a free-spinning wheel small movements aren't inverted while fast ones are — inconsistent. Given our primary devices are MX-class, this matters.
4. Per-event cost on the hottest path
The macOS tap must stay lock-light or it stalls the whole input stream (see the comments in macos.rs). This adds an RwLock read per scroll event and, when active, a fresh CGEventSource::new + CGEvent allocation per event. On a high-rate free-spin stream that risks TapDisabledByTimeout / scroll stutter.
Suggested approach
Instead of suppress + reinject, mutate the event in place and return Keep. We already do in-place field writes elsewhere (crates/openlogi-core/src/binding.rs:1312,1327), and the same set_*_value_field works on the callback's &CGEvent. Negate/scale AXIS_1/AXIS_2 plus the POINT_DELTA_* and fixed-point fields and keep the event — that preserves momentum, pixel precision, flags, and continuity, with zero allocation and no re-entry concern. For #126, branch on kCGScrollWheelEventIsContinuous to apply inversion to discrete (mouse) scroll only. This is essentially how Scroll Reverser does it.
Unrelated change
The default gesture remap (PrevTab/NextTab → PreviousDesktop/NextDesktop) is a separate UX decision — please split it into its own PR so each can be reviewed/reverted independently.
The config plumbing, clamping, and default fast-path are clean; the concern is purely the macOS transform path. Happy to help iterate on the in-place version.
|
Looking forward to this feature! :) |
|
Thanks again for putting this together — it helped clarify the scroll settings direction. The per-device inverted scrolling part has now landed separately in #294 and is available in v0.6.16, so this PR is mostly superseded for that use case. Since the remaining pieces here (scroll strength multiplier, tactility/chunking, and default gesture-swipe changes) are distinct behavior changes, I'm going to close this PR rather than keep a broad overlapping branch open. If you still want to pursue any of the remaining pieces, a smaller PR focused on just one behavior would be very welcome. Thanks for the contribution! |
|
gg |

Summary
This proposes two related mouse-control improvements:
Implementation notes
AppSettingsand mirrored into the hook runtime through a sharedScrollSettingsvalue.Verification
Ran locally on macOS:
cargo fmt --all cargo check -p openlogi-gui cargo test -p openlogi-gui -p openlogi-coreResults:
openlogi-core: 34 passedopenlogi-gui: 14 passed